#####################################
##       PEinjector/loader         ##
#####################################

import utils
import os
import shutil
import log
import json
import traceback
import config
import action

loaded_package = []
loaderr_pkgs = []
disk = ""
lists = []


def __version_parse(version: str) -> list:
    return [int(i) for i in version.split(".")]


def __version_compare(ver1: str, ver2: str) -> int:
    version1 = __version_parse(ver1)
    version2 = __version_parse(ver2)
    for (i, j) in zip(version1, version2):
        if i != j:
            return 1 if i > j else -1
    return 0


def version_check(configuration: dict, pkg_name: str) -> int:
    ERROR_MESSAGE = "load moudle [{}] failed: PEinjector version too {}, need [{}]"
    try:
        if not "compatibility" in configuration or not "injector" in configuration["compatibility"]:
            return 0
        with open(f"{disk}/PEinjector/VERSION", 'r', encoding="utf-8") as f:
            version = f.readlines()[0].rstrip("\n\r")

        if "min" in configuration["compatibility"]["injector"]:
            plugver = configuration["compatibility"]["injector"]["min"]
            if __version_compare(version, plugver) == -1:
                log.warn(ERROR_MESSAGE.format(pkg_name, "low", plugver))
                return 5

        if "max" in configuration["compatibility"]["injector"]:
            plugver = configuration["compatibility"]["injector"]["max"]
            if __version_compare(version, plugver) == 1:
                log.warn(ERROR_MESSAGE.format(pkg_name, "high", plugver))
                return 6

    except Exception as e:
        log.warn(f"load moudle [{pkg_name}] failed: {e}")
        return 7
    return 0


def file_check(file_json: dict, pkg_name: str) -> int:
    try:
        for i in file_json.get("compatibility", {}).get("file", {}).get("must", []):
            if not os.path.exists(i):
                log.warn(f"load moudle [{pkg_name}] failed: " +
                         f"Cannot find file: [{i}]")
                return 8
        for i in file_json.get("compatibility", {}).get("file", {}).get("mustnot", []):
            if os.path.exists(i):
                log.warn(f"load moudle [{pkg_name}] failed: " +
                         f"Find incompatible file: [{i}]")
                return 9
    except:
        log.warn(f"load moudle [{pkg_name}] failed: " +
                 "Unknown error in file check")
        return 10
    return 0


actions = {"onboot": [], "onload": []}


def load_package(pkg_name: str) -> int:
    pkg_path = f"{disk}/PEinjector/package/{pkg_name}"
    loaded_package.append(pkg_name)
    if "manifest.json" not in os.listdir(pkg_path):
        log.warn(f"load moudle [{pkg_name}] failed: " +
                 "Cannot find manifest.json")
        return 1
    try:
        with open(pkg_path+"/"+"manifest.json", "r", encoding="utf-8") as file:
            file_json = json.load(file)
    except json.decoder.JSONDecodeError:
        log.warn(f"load moudle [{pkg_name}] failed: " +
                 "Json syntax error")
        return 3
    except Exception as exp:
        log.warn(f"load moudle [{pkg_name}] failed: " +
                 "Unknown error in read file ("+repr(exp)+")")
        return 2
    for i in ("version", "name", "author", "introduce"):
        if i not in file_json:
            log.warn(f"load moudle [{pkg_name}] failed: " +
                     f"\"{i}\" key not in manifest.json")
            return 4
    retvar = version_check(file_json, pkg_name)
    if retvar != 0:
        return retvar
    retvar = file_check(file_json, pkg_name)
    if retvar != 0:
        return retvar
    if "dependence" in file_json:
        try:
            for i in file_json["dependence"]:
                if i not in lists:
                    log.warn(f"load moudle [{pkg_name}] failed: " +
                             f"Cannot find dependence [{i}]")
                    return 11
                if retvar in loaderr_pkgs:
                    log.warn(f"load moudle [{pkg_name}] failed: " +
                             f"dependence [{i}] loaded failed")
                    return 12
                if retvar not in loaded_package:
                    log.info(f"load moudle [{i}] from [{pkg_name}]")
                    retvar = load_package(i)
                    if retvar != 0:
                        loaderr_pkgs.append(i)
                        log.warn(f"load moudle [{pkg_name}] failed: " +
                                 f"dependence [{i}] loaded failed")
                        return 12
        except:
            log.warn(f"load moudle [{pkg_name}] failed: " +
                     f"Cannot find dependence [{i}]")
    data_list = []
    if "data" in file_json:
        j = -1
        prep_flag = False
        if pkg_name not in os.listdir(config.DATAPATH.replace("{DISK}", utils.find_disk())):
            log.info(f"prep moudle [{pkg_name}] data")
            prep_flag = True
        for i in file_json["data"]:
            j += 1
            flag = False
            for event in ("from", "to"):
                if event not in i or i[event] == "":
                    log.warn(f"load moudle [{pkg_name}] warning: " +
                             f"Load data syntax error on {event}[{j}] (lost \"{event}\"), igrone")
                    flag = True
                    break
            if flag:
                continue
            data_list.append(i)
            if prep_flag:
                data_dir = config.DATAPATH.replace(
                    "{DISK}", utils.find_disk())+"/"+pkg_name
                os.mkdir(data_dir)
                if os.path.isfile(pkg_path+"/"+i["from"]):
                    shutil.copyfile((
                        pkg_path+"/"+i["from"]).replace("/", "\\"), (data_dir+"/"+i["to"]))
                else:
                    shutil.copytree(
                        pkg_path+"/"+i["from"], data_dir+"/"+i["to"])

    add_action_head = False
    if "load" in file_json:
        open_symlink = config.USE_SYMLINK
        if "symlink" in file_json["load"] and file_json["load"] == False:
            open_symlink = False
        if "mode" in file_json["load"]:
            for event in ("onboot", "onload"):
                if event not in file_json["load"]["mode"]:
                    continue
                actions[event].append((0, pkg_name))
                if event == "onload":
                    add_action_head = True
                if type(file_json["load"]["mode"][event]) != list:
                    log.warn(f"load moudle [{pkg_name}] failed: " +
                             f"Load commands syntax error on {event} (must be a list)")
                    return 13
                j = -1
                for i in file_json["load"]["mode"][event]:
                    j += 1
                    if "type" not in i:
                        log.warn(f"load moudle [{pkg_name}] warning: " +
                                 f"Load commands syntax error on {event}[{j+1}] (lost \"type\"), igrone")
                        continue
                    if i["type"] == "force_copy" or (i["type"] == "copy" and open_symlink == False):
                        flag = False
                        for check in ("from", "to"):
                            if check not in i:
                                flag = True
                                log.warn(f"load moudle [{pkg_name}] warning: " +
                                         f"Load commands syntax error on {event}[{j+1}] (lost \"{check}\"), igrone")
                                break
                        if flag:
                            continue
                        if not os.path.exists(pkg_path+"/"+i["from"]):
                            log.warn(f"load moudle [{pkg_name}] warning: " +
                                     f"Load commands file error on {event}[{j+1}] (cannot find \"{i['from']}\"), igrone")
                            continue
                        actions[event] += action.force_copy(pkg_path,
                                                            pkg_name, data_list, i["from"], i["to"])
                    elif i["type"] == "copy":
                        flag = False
                        for check in ("from", "to"):
                            if check not in i:
                                flag = True
                                log.warn(f"load moudle [{pkg_name}] warning: " +
                                         f"Load commands syntax error on {event}[{j+1}] (lost \"{check}\"), igrone")
                                break
                        if flag:
                            continue
                        if not os.path.exists(pkg_path+"/"+i["from"]):
                            log.warn(f"load moudle [{pkg_name}] warning: " +
                                     f"Load commands file error on {event}[{j+1}] (cannot find \"{i['from']}\"), igrone")
                            continue
                        actions[event] += action.copy(pkg_path, pkg_name, data_list,
                                                      i["from"], i["to"])
                    elif i["type"] == "start":
                        flag = False
                        for check in ("command", ):
                            if check not in i:
                                flag = True
                                log.warn(f"load moudle [{pkg_name}] warning: " +
                                         f"Load commands syntax error on {event}[{j+1}] (lost \"{check}\"), igrone")
                                break
                        if flag:
                            continue
                        actions[event] += action.start(
                            pkg_name, data_list, i["command"])
                    else:
                        log.warn(f"load moudle [{pkg_name}] warning: " +
                                 f"Load commands syntax error on {event}[{j+1}] (unknown type), igrone")
    if "start" in file_json:
        if not add_action_head:
            actions[event].append((0, pkg_name))
        if "icon" in file_json["start"]:
            for i in ("command", "name", "icon"):
                if i not in file_json["start"]["icon"]:
                    log.warn(f"load moudle [{pkg_name}] warning: " +
                             f"Load icon syntax error (lost \"{i}\"), igrone")
            if len(file_json["start"]["icon"]["icon"]) < 2 or file_json["start"]["icon"]["icon"][1] != ':':
                file_json["start"]["icon"]["icon"] = pkg_path + \
                    "/"+file_json["start"]["icon"]["icon"]
            if len(file_json["start"]["icon"]["command"]) < 2 or file_json["start"]["icon"]["command"][1] != ':':
                file_json["start"]["icon"]["command"] = pkg_path + \
                    "/"+file_json["start"]["icon"]["command"]
            actions["onload"].append(
                (6, file_json["start"]["icon"]["command"], file_json["start"]["icon"]["icon"], file_json["start"]["icon"]["name"]))
        if "path" in file_json["start"]:
            for i in file_json["start"]["path"]:
                if len(i) < 2 or i[1] != ':':
                    i = pkg_path + \
                        "/"+i
                actions["onload"].append((7, i))
    if "reg" in file_json:
        for i in file_json["reg"]:
            if len(i) < 2 or i[1] != ':':
                i = pkg_path + \
                    "/"+i
            actions["onload"].append((8, i))


def load():
    try:
        global disk, lists
        log.info("start load")
        disk = utils.find_disk()
        lists = os.listdir(f"{disk}/PEinjector/package")
        with open(config.DISABLEPATH.replace("{DISK}", disk), "r") as file:
            disable_packages = [i.rstrip("\n\r") for i in file.readlines()]
        for packs in lists:
            if packs not in loaded_package and packs not in disable_packages:
                log.info(f"load moudle [{packs}]")
                retvar = load_package(packs)
                if retvar == 0:
                    loaderr_pkgs.append(packs)
        action.save_action(actions["onload"])
        alog = action.do_action(actions["onboot"])
        with open(config.ACTIONLOGPATH.replace("{DISK}", utils.find_disk()), "w") as file:
            file.write(alog)
    except Exception as exp:
        log.break_err("Exception \n"+str(traceback.format_exc(exp)))
        raise exp
